/**
 *  @file HttpServerEndPoint.cpp
 */
#include "HttpServerEndPoint.h"
#include "HttpRequest.h"
#include "HttpResponse.h"
#include "HttpException.h"
#include "message/IHttpBody.h"
#include "../../lang/enumT.h"
#include "../../io/BufferedReadStream.h"
#include "../SocketStream.h"
#include "../ssl/SSLSocket.h"
#include "../../convert/Convert.h"
#include "util/HttpUtility.h"

using namespace cppflib::net::http::message;
using namespace cppflib::lang;
using namespace cppflib::io;

namespace cppflib
{

namespace net
{

namespace http
{


HttpServerEndPoint::HttpServerEndPoint(void)
{
}

HttpServerEndPoint::HttpServerEndPoint(HttpParams * pParams) : HttpEndPoint(pParams)
{
   pReadStream = NULL;
   pParams->SetParam(HttpParams::P_ISSERVER, true);
}

HttpServerEndPoint::~HttpServerEndPoint(void)
{
   if (pReadStream)
      cppmempool::Release(pReadStream);
}

/**
 *  Parse request line
 *  
 *  @note Headers fetched is saved in HttpEndPoint::pTempBuf
 *
 *  @param [out] reqPath -- contains the request path if client doesn't specify an absolute path
 */
HttpRequest * HttpServerEndPoint::ParseRequestLine(CFString &reqPath)
{
   reqPath = _S("");

   do {
      CFString reqLine;
      if (!GetOneLine(reqLine)) 
         break;

      CFString method;                        // get method
      int pos = reqLine.Find(_S(' '));
      if (pos <= 0)
         break;
      method = reqLine.Left(pos);
      method.MakeUpper();
      httpmethod_t httpMethod = Enum::Parse<httpmethod_t>(method);
      reqLine.Delete(0, pos);
      reqLine.TrimLeft();

      HttpRequest *pRequest = new HttpRequest(httpMethod);  // make an instance of HttpRequest

      
      CFString reqUrl;                       // get request URL
      pos = reqLine.Find(_S(' '));
      if (pos <= 0)
         break;
      reqUrl = reqLine.Left(pos);
      if (reqUrl.StartsWith(http::PROTO_HTTP)) {
         if (IsSSLSocket())  // protocol not matched
            break;
         pRequest->SetUrl(reqUrl);
      }
      else if (reqUrl.StartsWith(http::PROTO_HTTPS)) {
         if (!IsSSLSocket()) // protocol not matched
            break;
         pRequest->SetUrl(reqUrl);
      }
      else {
         reqPath = reqUrl;   // return to caller
      }
      reqLine.Delete(0, pos);
      reqLine.TrimLeft();

      int endPos;
      CFString ver;                           // get HTTP version
      if (!__GetHttpVersion(reqLine, ver, endPos))   
         break;
      pRequest->SetHttpVersion(ver);
      
      return pRequest;

   } while(false);

   throw HttpException(_S("Invalid request line"), http::InvalidRequestLine);
   return NULL;
}

/**
 * Assign URL for the request if client doesn't specify an absolute URL
 */
void HttpServerEndPoint::SetRequestUrl(HttpRequest &req, const CFString &reqPath)
{
   CFString temp;
   CFString *pVal = NULL;
   pcwstr_t formatStr = (reqPath.StartsWith(_S('/'))) ? _S("%s%s") : _S("%s/%s");

   if (req.GetHeaderValue(CFString(_S("Host")), pVal)) {  // Host header exists
      temp.Format(formatStr, static_cast<pcwstr_t>(*pVal), static_cast<pcwstr_t>(reqPath));
   }
   else {
      CFString addr;
      u16_t port;

      GetSock().GetLocalAddress(addr, port);  // use local address instead
      temp.Format(formatStr, static_cast<pcwstr_t>(addr), static_cast<pcwstr_t>(reqPath));
   }

   if (IsSSLSocket()) // it is HTTPS
      temp.Insert(0, http::PROTO_HTTPS);

   req.SetUrl(temp);
}

/**
 *  Send a 100-continue to client
 */
void HttpServerEndPoint::SendContinueResponse(void)
{
   HttpResponse res(http::HTTP_VER_1_1, http::ST_CONTINUE, _S("Continue"));
   SendResponse(res);
}

/**
 *  Send status line
 */
void HttpServerEndPoint::SendStatusLine(HttpResponse &res)
{   
   CFString statusLine;
   statusLine.Format(_S("HTTP/%s %d %s\r\n"), 
                     static_cast<pcwstr_t>(res.GetHttpVersion()),
                     res.GetResponseCode(),
                     static_cast<pcwstr_t>(res.GetResponseReason()));

   if (!SendString(GetSock(), statusLine))
      throw HttpException(_S("Send status line error"));
}

/**
 *  Init default headers if not set by user
 */
void HttpServerEndPoint::InitDefaultHeaders(HttpMessage &httpMsg)
{
   CFString h(_S("Server"));
   if (!httpMsg.HeaderExists(h)) {
      const CFString *pServer = pParams->GetStrParam(HttpParams::P_SERVER);
      httpMsg.SetHeader(h, *pServer);
   }

   h = _S("Date");
   if (!httpMsg.HeaderExists(h)) {
      CFString date;
      util::GetRfc1123DateFormat(date);
      httpMsg.SetHeader(h, date);
   }

   HttpEndPoint::InitDefaultHeaders(httpMsg); // call base class, too
}

/**
 *  Get request from client
 */
HttpRequest *HttpServerEndPoint::GetRequest()
{
   if (!pReadStream) {  // init read stream
      if (GetSock().IsAttributeSet(Socket::SSL_SENDRECV_WITHSSL)) {
         // SSL socket requires a negotiation with the client
         static_cast<ssl::SSLSocket*>(&GetSock())->WaitHandshake(pParams->GetIntParam(HttpParams::P_SOTIMEOUT));
      }

      pReadStream = new BufferedReadStream(
                             new SocketStream(&GetSock(), pParams->GetIntParam(HttpParams::P_SOTIMEOUT)), 
                             HttpEndPoint::STREAMBUFFERSIZE);
      cppmempool::Retain(pReadStream);
   }

   GetHeaders(*pReadStream);
   CFString reqPath;
   HttpRequest *pRequest = ParseRequestLine(reqPath);
   ParseHeaders(*pRequest);
   if (!reqPath.IsEmpty())
      SetRequestUrl(*pRequest, reqPath);
   
   if (HttpMessage::CanRequestHaveBody(pRequest->GetMethod())) {
      // HTTP 1.1 client expects a 100-continue
      if (pRequest->IsVersion1Point1() && pRequest->ExpectContinueExists())  
         SendContinueResponse();

      // init body
      CFString *pVal = NULL;
      u64_t contentLength = (pRequest->GetHeaderValue(CFString("Content-Length"), pVal)) ? 
                                          convert::ToInt64(static_cast<pcwstr_t>(*pVal)) : 0;

      pRequest->SetHttpBody(new HttpPeerBody(pReadStream, contentLength, 
                                             pRequest->ChunkTransferEncodingExists(),
                                             pRequest->CompressedContentExists()));
   }
   
   return pRequest;
}

/**
 *  Send a response to client
 */
void HttpServerEndPoint::SendResponse(HttpResponse &res)
{
   InitDefaultHeaders(res);
   SendStatusLine(res);
   SendHeaders(res);
   SendBody(res);
}


} // end of namespace http

} // end of namespace net

} // end of namespace cppflib
